package org.dfl.core.model.analysis;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.dfl.core.model.TInputEdge;
import org.dfl.core.model.TOutputEdge;
import org.dfl.core.model.transitions.*;

public class RTransition extends RSimplePlace {
	
	private String functionLabel = "?";
	private Map<String, String> vars = new HashMap<String, String>();
	
	public RTransition() {};
	
	public RTransition(Transition t, Map<String, RPlace> places) {
		this.setId(t.getId());
		this.copyEdges(t, places);
		for (REdge e: this.getInputEdges()) {
			this.addVar(e.getId(), VariableNameFactory.getUVN(e.getLabel()));
		}
		
		if (t instanceof EmptyMultisetTransition) {
			TInputEdge e = t.getInputEdges().iterator().next();
			this.functionLabel = "empty(\n   " + vars.get(e.getId()) + "\n)"; 
		} else if (t instanceof MultisetTransition) {
			TInputEdge e = t.getInputEdges().iterator().next();
			this.functionLabel = "{\n   " + vars.get(e.getId()) + "\n}";
		} else if (t instanceof UnionTransition) {
			this.functionLabel = this.inputsToString(" + ");
		} else if (t instanceof FlattenTransition) {
			TInputEdge e = t.getInputEdges().iterator().next();
			this.functionLabel = "flatten(\n   " + vars.get(e.getId()) + "\n)";
		} else if (t instanceof CartesianProductTransition) {
			this.functionLabel = this.inputsToString(" x ");
		} else if (t instanceof EqualTransition) {
			this.functionLabel = this.inputsToString(" == ");
		} else if (t instanceof EmptyTupleConstructionTransition) {
			this.functionLabel = "<>";
		} else if (t instanceof TupleConstructionTransition) {
			this.functionLabel = "<\n" + this.inputsToTypedString() + "\n>";
		} else if (t instanceof PiTransition) {
			TInputEdge e = t.getInputEdges().iterator().next();
			this.functionLabel = vars.get(e.getId()) + "." + ((PiTransition) t).getSubkey();
		} else if (t instanceof IdTransition) {
			TInputEdge e = t.getInputEdges().iterator().next();
			this.functionLabel = "id(\n   " + vars.get(e.getId()) + "\n)";			
		} else if (t instanceof TavernaTransition) {
			this.functionLabel = t.getName() + "(\n" + this.inputsToTypedString() + "\n)";
		}
	}

	private void copyEdges(Transition t, Map<String, RPlace> places) {
		for (TInputEdge ie: t.getInputEdges()) {
			REdge e1 = new REdge(ie);
			int flag = REdge.E_BLANK;
			if (ie.isNeededEmpty()) { flag = REdge.E_EMPTY; }
			if (ie.isNeededFalse()) { flag = REdge.E_FALSE; }
			if (ie.isNeededNotempty()) { flag = REdge.E_NOTEMPTY; }
			if (ie.isNeededTrue()) { flag = REdge.E_TRUE; }
			if (ie.isNested()) { flag = REdge.E_NESTED; }
			e1.setFlag(flag);
			if (places.containsKey(ie.getPlace().getId())) {
				RPlace p1 = places.get(ie.getPlace().getId());
				if (p1 != null) {
					e1.connectPlaceToTransition(p1, this);
				}
			}
		}
		for (TOutputEdge oe: t.getOutputEdges()) {
			REdge e2 = new REdge(oe);
			int flag = REdge.E_BLANK;
			if (oe.isNeededEmpty()) { flag = REdge.E_EMPTY; }
			if (oe.isNeededFalse()) { flag = REdge.E_FALSE; }
			if (oe.isNeededNotempty()) { flag = REdge.E_NOTEMPTY; }
			if (oe.isNeededTrue()) { flag = REdge.E_TRUE; }
			if (oe.isNested()) { flag = REdge.E_NESTED; }
			e2.setFlag(flag);
			if (places.containsKey(oe.getPlace().getId())) {
				RPlace p2 = places.get(oe.getPlace().getId());
				if (p2 != null) {
					e2.connectTransitionToPlace(this, p2);
				}
			}
		}

	}
	public void setFunctionLabel(String s) {
		this.functionLabel = s;
	}
	
	public String getFunctionLabel() {
		return this.functionLabel;
	}
	
	public void addVar(String key, String var) {
		this.vars.put(key, var);
	}
	
	public String getVar(String key) {
		return this.vars.get(key);
	}
	
	public void removeVar(String key) {
		this.vars.remove(key);
	}
	
	public void copyVar(RTransition t, String newKey) {
		for (String key: t.getVars().keySet()) {
			String var = t.getVar(key);
			this.addVar(newKey, var);
		}
	}
	
	public String inputsToString(String delimiter) {
		String ret = "(\n";
		Iterator<REdge> ei = this.getInputEdges().iterator();
		while (ei.hasNext()) {
			REdge e = ei.next();
			ret += "   " + vars.get(e.getId());
			if (ei.hasNext()) {
				ret += delimiter + "\n";
			}
		}
		ret += "\n)";
		return ret;
	}
	
	public String inputsToTypedString() {
		String ret = "";
		Iterator<REdge> ei = this.getInputEdges().iterator();
		while (ei.hasNext()) {
			REdge e = ei.next();
			ret += "   " + e.getLabel() + ":" + vars.get(e.getId());
			if (ei.hasNext()) {
				ret += ",\n";
			}
		}
		return ret;
	}
	
	public Map<String, String> getVars() {
		return this.vars;
	}
	
	public void reconnectOutputEdges(RTransition t) {
		this.disconnectAllOutputEdges();
		for (REdge e: t.getOutputEdges()) {
			RPlace p = e.getPlace();
			e.connectTransitionToPlace(this, p);
		}
		t.removeOutputEdges();
	}
	
	public void disconnectAllInputEdges() {
		for (REdge e: this.getInputEdges()) {
			e.getPlace().removeOutputEdge(e);
		}
		this.removeInputEdges();
	}
	
	public void disconnectAllOutputEdges() {
		for (REdge e: this.getOutputEdges()) {
			e.getPlace().removeInputEdge(e);
		}
		this.removeOutputEdges();
	}
	
	public boolean equalsInputExcept(RTransition t, RPlace p) {
		if (t.getInputEdges().size() == this.getInputEdges().size()) {
			int matched = 0;
			for (REdge e1: this.getInputEdges()) {
				boolean found = false;
				Iterator<REdge> ei = t.getInputEdges().iterator();
				while ((!found) && (ei.hasNext())) {
					REdge e2 = ei.next();
					if (e1.getPlace().equals(e2.getPlace())) {
						found = true;
						if ((e1.getFlag() == e2.getFlag()) || (e1.getPlace().equals(p))) {
							matched++;
						}
					}
				}
			}
			if (matched == this.getInputEdges().size()) {
				return true;
			}
		}
		return false;
	}

	public boolean equalsOutput(RTransition t) {
		if (t.getOutputEdges().size() == this.getOutputEdges().size()) {
			int matched = 0;
			for (REdge e1: this.getOutputEdges()) {
				boolean found = false;
				Iterator<REdge> ei = t.getOutputEdges().iterator();
				while ((!found) && (ei.hasNext())) {
					REdge e2 = ei.next();
					if (e1.getPlace().equals(e2.getPlace())) {
						found = true;
						if (e1.getFlag() == e2.getFlag()) {
							matched++;
						}
					}
				}
			}
			if (matched == this.getOutputEdges().size()) {
				return true;
			}
		}
		return false;
	}
}
